/**
 * FilterMainFrame.java
 * 
 * @date: 2012-02-08
 * @author: Xiaoyu Guo (MUN# 200982155)
 * This file is part of the course project for COMP 6752, 
 * Application of Computer Graphics. All the file is written
 * by myself.
 */
package mun.cs6752.project;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JSplitPane;
import javax.swing.JMenuBar;
import javax.swing.JToolBar;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

import mun.cs6752.project.filter.DoNothing.DoNothingFilter;
import mun.cs6752.project.filter.dithering.DitherFilter;
import mun.cs6752.project.filter.featureWarping.FeatureWarpingFilter;
import mun.cs6752.project.filter.gray.GrayFilter;
import mun.cs6752.project.filter.interfaces.IImageFilter;
import mun.cs6752.project.filter.interfaces.IImageProvider;
import mun.cs6752.project.filter.localContrast.LocalContrastFilter;
import mun.cs6752.project.filter.resize.ResizeFilter;
import mun.cs6752.project.filter.threshold.ThresholdFilter;
import mun.cs6752.project.model.WorkSpaceStatus;
import mun.cs6752.project.ui.ImageCanvas;
import mun.cs6752.project.ui.ImageFilterModel;
import mun.cs6752.project.ui.fileChooser.SaveImageFileChooser;
import mun.cs6752.project.ui.fileChooser.ImageFileFilter;
import mun.cs6752.project.ui.transfer.ImageImportTransferHandler;

import java.awt.image.BufferedImage;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

import visreed.swing.SwingHelper;

import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import javax.swing.JProgressBar;

/**
 * The main frame
 * @author Xiaoyu Guo
 */
public class FilterMainFrame extends JFrame implements IImageProvider {
    private static final long serialVersionUID = -872035557333190458L;
    
    /* variables */
    protected List<IImageFilter> filters;
    protected WorkSpaceStatus workSpaceStatus;
    
    /* controls */ 
    protected BufferedImage sourceImage;
    protected BufferedImage targetImage;

    private JPanel pnlFilterConfig;
    private JFileChooser fcOpenFile;
    private JFileChooser fcSaveFile;

    private JScrollPane spLeftImage;
    private ImageCanvas canvasLeftImage;
    private JScrollPane spRightImage;
    private ImageCanvas canvasRightImage;

    private JMenuItem mntmNew;
    private JMenuItem mntmOpen;
    private JMenuItem mntmSave;
    private JMenuItem mntmSaveAs;
    private JMenuItem mntmClose;

    private JProgressBar pbProgress;
    
    public FilterMainFrame() {
        initFilters();
        
        sourceImage = null;
        targetImage = null;
        
        initUI();

        updateWorkSpaceStatus(WorkSpaceStatus.Empty);
    }
    
    private void initFilters() {
        filters = new ArrayList<IImageFilter>();
        filters.add(new DitherFilter(this));
        filters.add(new DoNothingFilter(this));
        filters.add(new LocalContrastFilter(this));
        filters.add(new ThresholdFilter(this));
        filters.add(new ResizeFilter(this));
        filters.add(new FeatureWarpingFilter(this));
        filters.add(new GrayFilter(this));
    }

    private void initUI() {
        setTitle("Image Filter");
        
        /* Menu */
        
        JPanel pnlTop = new JPanel();
        getContentPane().add(pnlTop, BorderLayout.NORTH);
        pnlTop.setLayout(new BorderLayout(0, 0));
        
        JMenuBar nmMainMenu = new JMenuBar();
        pnlTop.add(nmMainMenu, BorderLayout.NORTH);
        
        JMenu mnFile = new JMenu("File");
        mnFile.setMnemonic('F');
        nmMainMenu.add(mnFile);
        
        this.mntmNew = new JMenuItem("New...");
        mntmNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK));
        mnFile.add(mntmNew);
        
        this.mntmOpen = new JMenuItem("Open...");
        mntmOpen.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                openImage();
            }
        });
        mntmOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
        mntmOpen.setMnemonic('O');
        mnFile.add(mntmOpen);
        
        this.mntmSave = new JMenuItem("Save");
        mntmSave.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                saveImage(fcOpenFile.getSelectedFile());
            }
        });
        mntmSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));
        mnFile.add(mntmSave);
        
        this.mntmSaveAs = new JMenuItem("Save As...");
        mntmSaveAs.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                saveImageAs();
            }
        });
        mnFile.add(mntmSaveAs);
        
        this.mntmClose = new JMenuItem("Close");
        mntmClose.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                closeImage();
            }
        });
        mntmClose.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_MASK));
        mnFile.add(mntmClose);
        
        mnFile.addSeparator();
        
        JMenuItem mntmExit = new JMenuItem("Exit");
        mntmExit.setMnemonic('X');
        mnFile.add(mntmExit);
        
        /* Toolbar */
        
        JToolBar tbMain = new JToolBar();
        pnlTop.add(tbMain, BorderLayout.SOUTH);
        
        /* Image canvas */
        
        JSplitPane spImages = new JSplitPane();
        spImages.setResizeWeight(0.5);
        getContentPane().add(spImages, BorderLayout.CENTER);
        
        JPanel pnlLeftImage = new JPanel();
        spImages.setLeftComponent(pnlLeftImage);
        pnlLeftImage.setLayout(new BorderLayout(0, 0));
        
        JLabel lblLeftImage = new JLabel("Left Image");
        lblLeftImage.setHorizontalAlignment(SwingConstants.CENTER);
        pnlLeftImage.add(lblLeftImage, BorderLayout.NORTH);
        
        this.spLeftImage = new JScrollPane();
        pnlLeftImage.add(spLeftImage, BorderLayout.CENTER);
        
        this.canvasLeftImage = new ImageCanvas();
        spLeftImage.setViewportView(canvasLeftImage);
        
        JPanel pnlRightImage = new JPanel();
        spImages.setRightComponent(pnlRightImage);
        pnlRightImage.setLayout(new BorderLayout(0, 0));
        
        JLabel lblRightImage = new JLabel("Right Image");
        lblRightImage.setHorizontalAlignment(SwingConstants.CENTER);
        pnlRightImage.add(lblRightImage, BorderLayout.NORTH);
        
        this.spRightImage = new JScrollPane();
        pnlRightImage.add(spRightImage, BorderLayout.CENTER);
        
        this.canvasRightImage = new ImageCanvas();
        spRightImage.setViewportView(canvasRightImage);
        
        /* Config panel */
        
        final JPanel pnlConfig = new JPanel();
        getContentPane().add(pnlConfig, BorderLayout.EAST);
        pnlConfig.setLayout(new BorderLayout(0, 0));
        
        JComboBox cbFilterSeleciton = new JComboBox(new ImageFilterModel(filters));
        cbFilterSeleciton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                pnlConfig.remove(pnlFilterConfig);
                IImageFilter selectedFilter = ((ImageFilterModel)((JComboBox)e.getSource()).getModel()).getSelectedFilter();
                ImageCanvas srcCanvas = null;
                if(selectedFilter.isOverridingSourceImageControl()){
                    srcCanvas = selectedFilter.getSourceImageControl(sourceImage);
                } else {
                    srcCanvas = new ImageCanvas(sourceImage);
                }
                canvasLeftImage = srcCanvas;
                spLeftImage.setViewportView(canvasLeftImage);
                
                if(selectedFilter.isOverridingTargetImageControl()){
                    srcCanvas = selectedFilter.getTargetImageControl();
                } else {
                    srcCanvas = new ImageCanvas();
                }
                canvasRightImage = srcCanvas;
                spRightImage.setViewportView(canvasRightImage);
                
                pnlFilterConfig = selectedFilter.getControl();
                pnlConfig.add(pnlFilterConfig, BorderLayout.CENTER);
                pnlConfig.revalidate();
            }
        });
        pnlConfig.add(cbFilterSeleciton, BorderLayout.NORTH);
        
        this.pnlFilterConfig = new JPanel();
        pnlConfig.add(pnlFilterConfig, BorderLayout.EAST);
        
        JPanel pnlControls = new JPanel();
        getContentPane().add(pnlControls, BorderLayout.SOUTH);
        pnlControls.setLayout(new BorderLayout(0, 0));
        
        this.pbProgress = new JProgressBar();
        pnlControls.add(pbProgress);
        
        /* File Choosers */
        this.fcOpenFile = new JFileChooser();
        fcOpenFile.setMultiSelectionEnabled(false);
        fcOpenFile.setFileSelectionMode(JFileChooser.FILES_ONLY);
        for(FileFilter f : ImageFileFilter.FILE_FILTERS){
            fcOpenFile.addChoosableFileFilter(f);
        }
        fcOpenFile.setFileFilter(new FileNameExtensionFilter("All Supported Images", ImageIO.getReaderFileSuffixes()));
        
        this.fcSaveFile = new SaveImageFileChooser();

        // drag in image to open
        this.setTransferHandler(new ImageImportTransferHandler());
        
        setMinimumSize(new Dimension(800, 600));
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        workSpaceStatus = WorkSpaceStatus.Empty;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        SwingHelper.setSystemLookAndFeel();
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                FilterMainFrame frame = new FilterMainFrame();
                frame.setVisible(true);
            }
        });
    }
    
    private void updateWorkSpaceStatus(WorkSpaceStatus newStatus){
        switch(newStatus){
        case Empty:
            this.mntmClose.setEnabled(false);
            this.mntmSave.setEnabled(false);
            this.mntmSaveAs.setEnabled(false);
            break;
        case Clean:
            this.mntmClose.setEnabled(true);
            this.mntmSave.setEnabled(false);
            this.mntmSaveAs.setEnabled(true);
            break;
        case Dirty:
            this.mntmClose.setEnabled(true);
            this.mntmSave.setEnabled(true);
            this.mntmSaveAs.setEnabled(true);
        }
    }
    
    public void openImage(){
        int result = this.fcOpenFile.showOpenDialog(this);
        if(result == JFileChooser.APPROVE_OPTION){
            openImage(fcOpenFile.getSelectedFile());
        }
    }
    
    public void openImage(File src){
        try {
            this.sourceImage = ImageIO.read(src);
            this.setSrcImage(sourceImage);
            this.setTargetImage(null);
            this.fcOpenFile.setSelectedFile(src);
            updateWorkSpaceStatus(WorkSpaceStatus.Clean);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private void saveImage(File dest){
        try{
            String selectedFileExt = dest.getName();
            int tmpPos = selectedFileExt.lastIndexOf(".");
            if(tmpPos < 0){
                throw new Exception("File type is not supported.");
            }
            selectedFileExt = selectedFileExt.substring(tmpPos + 1);
            
            if(selectedFileExt.equals("jpg") || selectedFileExt.equals("jpeg")){
                // write image with explicit compression parameters (for jpg)
                Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix(selectedFileExt);
                ImageWriter writer = iter.next();
                ImageWriteParam iwp = writer.getDefaultWriteParam();
                iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                iwp.setCompressionQuality(0.9F);
                writer.setOutput(new FileImageOutputStream(dest));
                writer.write(null, new IIOImage(targetImage, null, null), iwp);
            } else {
                ImageIO.write(targetImage, selectedFileExt, dest);
            }
        } catch (IOException e){
            JOptionPane.showMessageDialog(
                this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void saveImageAs(){
        int result = this.fcSaveFile.showSaveDialog(this);
        if(result == JFileChooser.APPROVE_OPTION){
            saveImage(fcSaveFile.getSelectedFile());
            updateWorkSpaceStatus(WorkSpaceStatus.Clean);
        }
    }
    
    public void closeImage(){
        if(this.workSpaceStatus .equals(WorkSpaceStatus.Dirty)){
            int saveChoice = JOptionPane.showConfirmDialog(this, "Do you like to save your image first?");
            if(saveChoice == JOptionPane.YES_OPTION){
                saveImageAs();
            } else if (saveChoice == JOptionPane.CANCEL_OPTION){
                return;
            }
        }
        this.setSrcImage(null);
        this.setTargetImage(null);
        this.fcOpenFile.setSelectedFile(null);
        updateWorkSpaceStatus(WorkSpaceStatus.Empty);
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageProvider#getSourceImage()
     */
    @Override
    public BufferedImage getSourceImage() {
        return this.sourceImage;
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageProvider#getPreviewImage()
     */
    @Override
    public BufferedImage getPreviewImage() {
        // TODO Auto-generated method stub
        return this.sourceImage;
    }
    
    public void setSrcImage(BufferedImage image){
        this.sourceImage = image;
        this.canvasLeftImage.setImage(image);
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageProvider#setDestImage(java.awt.image.BufferedImage)
     */
    @Override
    public void setTargetImage(BufferedImage image) {
        if(image == null){
            this.targetImage = null;
            this.canvasRightImage.setImage(null);
            updateWorkSpaceStatus(WorkSpaceStatus.Empty);
        } else if (!image.equals(this.targetImage)){
            this.targetImage = image;
            this.canvasRightImage.setImage(image);
            updateWorkSpaceStatus(WorkSpaceStatus.Dirty);
        }
    }

}
